﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using WebChatApi.Common.Interface;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace WebChatApi.Common
{
    /// <summary>
    /// Class for dynamic object.
    /// </summary>
    [DataContract]
    public class DynamicObject
    {
        /// <summary>
        /// The properties
        /// </summary>
        [DataMember]
        protected Dictionary<string, object> properties = new Dictionary<string, object>();

        /// <summary>
        /// Gets the properties.
        /// </summary>
        /// <value>
        /// The properties.
        /// </value>
        public ICollection<string> Properties
        {
            get
            {
                return properties.Keys;
            }
        }

        /// <summary>
        /// Gets the object name as setting identity.
        /// </summary>
        /// <value>
        /// The object name as setting identity.
        /// </value>
        public virtual string ObjectNameAsSettingIdentity
        {
            get;
            protected set;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicObject" /> class.
        /// </summary>
        public DynamicObject()
            : this(null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicObject"/> class.
        /// </summary>
        /// <param name="objectNameAsSettingIdentity">The object name as setting identity.</param>
        public DynamicObject(string objectNameAsSettingIdentity)
        {
            this.ObjectNameAsSettingIdentity = string.IsNullOrWhiteSpace(objectNameAsSettingIdentity) ? "DynamicObject" : objectNameAsSettingIdentity;
        }

        ///// <summary>
        ///// Gets or sets the <see cref="System.Object"/> with the specified name.
        ///// </summary>
        ///// <value>
        ///// The <see cref="System.Object"/>.
        ///// </value>
        ///// <param name="name">The name.</param>
        ///// <returns></returns>
        //public object this[string name]
        //{
        //    get
        //    {
        //        return GetValue(name);
        //    }
        //    set
        //    {
        //        SetValue(name, value, true);
        //    }
        //}

        #region Methods

        /// <summary>
        /// To the dictionary.
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, object> ToDictionary()
        {
            Dictionary<string, object> result = new Dictionary<string, object>();

            foreach (var key in this.properties.Keys)
            {
                result.Add(key, this.properties[key]);
            }

            return result;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public object GetValue(string name)
        {
            return (!string.IsNullOrWhiteSpace(name) && properties.ContainsKey(name)) ? properties[name] : null;
        }

        /// <summary>
        /// Gets the value as.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public T GetValueAs<T>(string name) where T : class
        {
            return GetValue(name) as T;
        }

        /// <summary>
        /// Gets the value as T by default.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        public T GetValueAsTByDefault<T>(string name) where T : struct
        {
            object value = GetValue(name);
            return value == null ? default(T) : (T)value;
        }

        /// <summary>
        /// Sets the value.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public void SetValue(string name, object value = null)
        {
            if (!string.IsNullOrWhiteSpace(name))
            {
                if (properties.ContainsKey(name))
                {
                    properties[name] = value;
                }
                else
                {
                    properties.Add(name, value);
                }
            }
        }

        /// <summary>
        /// Sets the values by json.
        /// </summary>
        /// <param name="jsonString">The json string.</param>
        /// <param name="mappingCollection">The mapping collection.</param>
        public void SetValuesByJson(string jsonString, DynamicPropertyMappingCollection mappingCollection)
        {
            if (!string.IsNullOrWhiteSpace(jsonString) && mappingCollection != null)
            {
                //var jsonObject = JObject.Parse(jsonString);
                var jsonObject = JToken.Parse(jsonString);
                SetDynamicObject(this, jsonObject, mappingCollection);
            }
        }

        /// <summary>
        /// Gets the dynamic property mapping collection.
        /// </summary>
        /// <returns></returns>
        public DynamicPropertyMappingCollection GetDynamicPropertyMappingCollection()
        {
            return DynamicObjectSettings.GetDynamicPropertyMappingCollection(this.ObjectNameAsSettingIdentity);
        }

        #endregion

        #region Json

        /// <summary>
        /// To the json.
        /// </summary>
        /// <returns></returns>
        public string ToJson()
        {
            //return JsonConvert.SerializeObject(this.properties);

            StringBuilder stringbuilder = new StringBuilder();
            foreach (var key in this.properties.Keys)
            {
                var value = this.properties[key];
                string valueString;

                if (value == null)
                {
                    valueString = "undefined";
                }
                else
                {
                    DynamicObject valueObj = value as DynamicObject;
                    if (valueObj != null)
                    {
                        valueString = valueObj.ToJson();
                    }
                    else
                    {
                        valueString = JsonConvert.SerializeObject(value);
                    }
                }

                stringbuilder.AppendFormat("\"{0}\":{1},", key, valueString);
            }

            if (stringbuilder.Length > 0)
            {
                stringbuilder.Remove(stringbuilder.Length - 1, 1);
            }

            return "{" + stringbuilder.ToString() + "}";
        }

        #endregion

        /// <summary>
        /// Sets the dynamic object.
        /// </summary>
        /// <param name="dynamicObject">The dynamic object.</param>
        /// <param name="jsonObject">The json object.</param>
        /// <param name="mappingCollection">The mapping collection.</param>
        /// <returns></returns>
        protected static DynamicObject SetDynamicObject(DynamicObject dynamicObject, JToken jsonObject, DynamicPropertyMappingCollection mappingCollection)
        {
            if (dynamicObject == null)
            {
                dynamicObject = new DynamicObject();
            }

            if (jsonObject != null && mappingCollection != null)
            {
                foreach (var one in mappingCollection)
                {
                    object value = null;

                    if (one.ComplexObjectValue != null)
                    {
                        var complexJsonObj = jsonObject[one.SourceName];
                        if (complexJsonObj != null)
                        {
                            if (complexJsonObj.Type == JTokenType.Array)
                            {
                                if (one.ComplexObjectValue.MappingCount > 0)
                                {
                                    var temp = new List<DynamicObject>();
                                    foreach (var item in complexJsonObj.Children())
                                    {
                                        temp.Add(SetDynamicObject(null, item, one.ComplexObjectValue));
                                    }
                                    value = temp;
                                }
                                else
                                {
                                    var temp = new ArrayList();
                                    foreach (var item in complexJsonObj.Children())
                                    {
                                        temp.Add(item);
                                    }
                                    value = temp;
                                }
                            }
                            else
                            {
                                value = SetDynamicObject(null, complexJsonObj, one.ComplexObjectValue);
                            }
                        }
                    }
                    else
                    {
                        value = jsonObject[one.SourceName];
                    }


                    dynamicObject.SetValue(one.PropertyName, value);
                }

            }

            return dynamicObject;
        }

        /// <summary>
        /// Creates the dynamic object.
        /// </summary>
        /// <param name="objectTypeName">Name of the object type.</param>
        /// <param name="dataObject">The data object.</param>
        /// <param name="mappingCollection">The mapping collection.</param>
        /// <returns></returns>
        public static DynamicObject CreateDynamicObject(string objectTypeName, object dataObject, DynamicPropertyMappingCollection mappingCollection = null)
        {
            DynamicObject result = new DynamicObject();

            if (mappingCollection == null)
            {
                mappingCollection = DynamicObjectSettings.GetDynamicPropertyMappingCollection(objectTypeName);
                result.ObjectNameAsSettingIdentity = objectTypeName;
            }

            if (!string.IsNullOrWhiteSpace(objectTypeName) && dataObject != null && mappingCollection != null)
            {
                var objectProperties = AssemblyUtil.GetPropertyNames(dataObject);
                foreach (var one in mappingCollection)
                {
                    object value = null;

                    if (one.ComplexObjectValue != null)
                    {
                        IEnumerable enumerable = dataObject as IEnumerable;
                        if (enumerable != null)
                        {
                            var listContainer = new List<DynamicObject>();

                            foreach (var item in enumerable)
                            {
                                var tmp = CreateDynamicObject(one.SourceName, item, one.ComplexObjectValue);
                                if (tmp != null)
                                {
                                    listContainer.Add(tmp);
                                }
                            }

                            value = listContainer;
                        }
                        else
                        {
                            value = objectProperties.Contains(one.PropertyName) ? ReflectionUtil.GetPropertyValue(dataObject, one.SourceName) : null;
                            value = value != null ? CreateDynamicObject(one.SourceName, value, one.ComplexObjectValue) : null;
                        }
                    }
                    else
                    {
                        value = objectProperties.Contains(one.PropertyName) ? ReflectionUtil.GetPropertyValue(dataObject, one.SourceName) : null;
                    }

                    if (value == null)
                    {
                        value = one.DefaultValue;
                    }
                    result.SetValue(one.PropertyName, value);

                }
            }

            return result;
        }
    }
}
